package deepbrain.simnetwork.network;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import deepbrain.simnetwork.exception.SimNetworkRuntimeException;
import deepbrain.simnetwork.util.SimNetUtils;

/**
 * 
 * 
 * @author Li Yang
 * @create 2009-2-29
 */
public class NetworkState implements Serializable {

	private static final long serialVersionUID = 8918236120623749925L;

	protected NodeState[] nodeStates;

	public NetworkState() {
	}

	public NetworkState(NodeState[] nodeStates) {
		this.nodeStates = nodeStates;
	}

	/**
	 * Deep clone a new network state
	 */
	public NetworkState(NetworkState networkState2) {
		NodeState[] nodeStates2 = networkState2.getNodeStates();
		NodeState[] nodeStates = new NodeState[nodeStates2.length];
		for (int i = 0; i < networkState2.numOfNodes(); i++)
			nodeStates[i] = new NodeState(nodeStates2[i]);
		this.nodeStates = nodeStates;
	}

	public void setNodeStates(NodeState[] nodeStates) {
		this.nodeStates = nodeStates;
	}

	public NodeState[] getNodeStates() {
		return nodeStates;
	}

	public NodeState getNodeState(int n) {
		return nodeStates[n];
	}

	public double getProbability(NodeState[] certainStates) {
		if (certainStates == null || nodeStates.length != certainStates.length)
			throw new SimNetworkRuntimeException(
					"Certain States are Not Corret!");
		double prob = 1.0;
		for (int i = 0; i < nodeStates.length; i++)
			prob *= nodeStates[i].getProbability(certainStates[i]
					.isCertainFiring());
		return prob;
	}

	public double getProbability(boolean[] certainStates) {
		if (certainStates == null || nodeStates.length != certainStates.length)
			throw new SimNetworkRuntimeException(
					"Certain States are Not Corret!");
		double prob = 1.0;
		for (int i = 0; i < nodeStates.length; i++)
			prob *= nodeStates[i].getProbability(certainStates[i]);
		return prob;
	}

	public int numOfNodes() {
		if (nodeStates == null)
			return 0;
		else
			return nodeStates.length;
	}

	/**
	 * 000 -> 0 ; 001 -> 1 ; 010 -> 2 ....
	 */
	public int getIndex() {
		int index = 0;
		for (int i = 0; i < nodeStates.length; i++) {
			int p = nodeStates.length - i - 1;
			index += (nodeStates[p].isCertainFiring() ? 1 : 0) * Math.pow(2, i);
		}
		return index;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder(100);
		builder.append("[");
		for (int i = 0; i < nodeStates.length - 1; i++) {
			builder.append(nodeStates[i]);
			builder.append(", ");
		}
		builder.append(nodeStates[nodeStates.length - 1]);
		builder.append("]");
		return builder.toString();
	}

	public String toStringBrief() {
		StringBuilder builder = new StringBuilder(100);
		builder.append("[");
		for (int i = 0; i < nodeStates.length - 1; i++) {
			builder.append(nodeStates[i].toStringBrief());
			builder.append(", ");
		}
		builder.append(nodeStates[nodeStates.length - 1].toStringBrief());
		builder.append("]");
		return builder.toString();
	}

	public static NetworkState loadNetworkState(String string) {
		string = string.trim();
		int numOfNodes = string.length();
		NodeState[] states = new NodeState[numOfNodes];
		for (int i = 0; i < numOfNodes; i++) {
			if (string.charAt(i) == '1')
				states[i] = NodeState.CERTAIN_FIRING;
			else
				states[i] = NodeState.CERTAIN_SILENT;
		}
		return new NetworkState(states);
	}

	/**
	 * Get certain network state from probatilistic network state by random
	 * considering the probability of each node
	 * 
	 * @return
	 */
	public static NetworkState getRandomCertainNetwork(NetworkState pState) {
		NodeState[] state = new NodeState[pState.numOfNodes()];
		for (int i = 0; i < pState.numOfNodes(); i++) {
			state[i] = NodeState.getCertainState(pState.getNodeState(i)
					.getFiringProbability());
		}
		return new NetworkState(state);
	}

	@Override
	public int hashCode() {
		int hashCode = 0;
		for (int i = 0; i < nodeStates.length; i++)
			hashCode += nodeStates[i].hashCode();
		return hashCode;
	}

	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof NetworkState))
			return false;
		NetworkState objNetworkState = (NetworkState) obj;
		if (nodeStates.length != objNetworkState.nodeStates.length)
			return false;
		for (int i = 0; i < nodeStates.length; i++)
			if (!nodeStates[i].equals(objNetworkState.nodeStates[i]))
				return false;
		return true;
	}

	public static NetworkState combine(NetworkState state1, NetworkState state2) {
		NodeState[] newState = new NodeState[state1.numOfNodes()];
		for (int i = 0; i < state1.numOfNodes(); i++) {
			if ((state1.getNodeState(i) != null && state1.getNodeState(i)
					.isCertainFiring())
					|| (state2.getNodeState(i) != null && state2
							.getNodeState(i).isCertainFiring()))
				newState[i] = NodeState.CERTAIN_FIRING;
			else
				newState[i] = NodeState.CERTAIN_SILENT;
		}
		return new NetworkState(newState);
	}

	/**
	 * Get total probability distribution from marginal probability distribution
	 */
	public Map<NetworkState, Double> getProbabilityDistribution() {
		List<NetworkState> states = SimNetUtils
				.getAllCertainNetworkStates(nodeStates.length);
		Map<NetworkState, Double> distribution = new HashMap<NetworkState, Double>();
		for (NetworkState state : states) {
			double prob = getProbability(state.nodeStates);
			if (prob != 0)
				distribution.put(state, prob);
		}
		return distribution;
	}
}
